module jinji.rpc;

public import generated;
public import jinji.common.type : ETime, Id, StdTime;
import jinji.user.token;
public import jinji.db,
jinji.util.error;
public import jinji.util.tag : Splitter;
public import std.algorithm : min;
public import tame.conv : text;
public import tame.string : canFind;

alias Perm = Permissons;

version (Mock) {
	struct Action {
		string name;
	}

	template Mock() {
		template opDispatch(string member) {
			auto opDispatch(A...)(auto ref A args)
				=> this;

			auto opDispatch(T, A...)(auto ref A args)
				=> this;
		}
	}

	struct Request {
		auto read(T)() => T.init;
		auto read(T)(T) => T.init;
		int id;
		Id uid;
		bool opCast(T : bool)() const => false;
		mixin Mock;
	}

	alias WSClient = Request;

	//struct WSClient {
	//	mixin Mock;
	//}

	alias PeerID = int;

} else {
	import dast.wsrpc;

	alias Request = WSRequest;
}

/// 检查用户权限
void require(ref Request req, Perm perm) {
	const sess = sessions[req.src.id];
	if (!sess.uid)
		throw new Exception(_("请先登录"));
	if ((sess.perm & perm) != perm)
		throw new Exception(_("权限不足"));
}

/// 获取req对应的uid
auto uid(ref Request req) {
	const id = sessions[req.src.id].uid;
	if (!id)
		throw new Exception(_("请先登录"));
	return cast(Id)id;
}

/++
检查修改权限：若uid不是req.uid，则要求perm
Params:
	req = 请求
	uid = 用户id
	perm = 权限
+/
void checkPerm(ref Request req, Id uid, Perm perm = Perm.admin) {
	if (uid != req.uid)
		req.require(perm);
}

/// 获取当前时间
@property auto now() @trusted {
	version (Windows) {
		import core.sys.windows.winbase;

		enum hnsecsFrom1601 = 504_911_232_000_000_000L;
		long fileTime = void;
		GetSystemTimeAsFileTime(cast(FILETIME*)&fileTime);
		return cast(StdTime)(fileTime + hnsecsFrom1601);
	} else {
		import std.datetime.systime;

		try {
			return Clock.currStdTime;
		} catch (Exception)
			return cast(StdTime)long.max;
	}
}

/// 2025-01-01T00:00:00Z
enum EpochTime = 638_712_864_000_000_000;

@safe pure nothrow @nogc {
	ETime toETime(StdTime time)
		=> cast(ETime)((time - EpochTime) / 10_000_000);

	StdTime toStdTime(ETime time)
		=> cast(StdTime)(time * 10_000_000 + EpochTime);

	long toUnixTime(StdTime time)
		=> (time - StdTime.Epoch) / 10_000;
}

/// 获取当前时间戳（秒）
@property auto nowSec() => toETime(now);

/// 序列化对象
void sendObj(T)(ref Request req, in T obj) if (__traits(isPOD, T)) {
	import lmpl4d;

	auto p = &req.packer;
	if (const(T).init is obj) {
		p.beginMap(0);
		return;
	}
	p.beginMap(NumOfSerializingMembers!T);
	foreach (i, ref f; obj.tupleof) {
		static if (isPackedField!(T.tupleof[i])) {
			enum name = __traits(identifier, T.tupleof[i]);
			p.pack(name);
			static if (is(typeof(f) : StdTime))
				p.pack(toUnixTime(f)); // 转换时间戳
			else
				p.pack(f);
		}
	}
}

/// 会话
struct Session {
	Payload token;
@property pure @nogc nothrow:
	/// UID
	auto uid() const => token.id;
	ref uid() => token.id;
	/// 权限
	Perm perm() const => cast(Perm)token.data;

	WSClient client;
	alias client this;
}

/// 会话组
struct Sessions {
	Session[PeerID] session;
	alias session this;

	auto opIndex(PeerID key) {
		if (auto p = key in session)
			return *p;
		return Session();
	}

	void opIndexAssign(Session value, PeerID key) @safe pure nothrow {
		session[key] = value;
	}
}

/// 全局会话
__gshared Sessions sessions;
